#include "sockclient.h"

#include <errno.h> //errno
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>

///////////////////////////////////////////////////////////
///////////////////////client//////////////////////////////

ClientSock::ClientSock() : SockBase()
{
}

ClientSock::~ClientSock()
{
}

int ClientSock::SetAsync(int fd, bool bAsync)
{
    int ret = fcntl(fd, F_GETFL, 0);
    if (-1 != ret) {
        if (bAsync) {
            ret = fcntl(fd, F_SETFL, ret | O_NDELAY);
        } else {
            ret = fcntl(fd, F_SETFL, ret & ~O_NDELAY);
        }
    }
    return ret;
}

/* pur @ 地址本连接函数设置句柄参数函数
 * para @ fd 要设置的socket句柄
*/
void ClientSock::SetConnectOpt(int fd)
{
    int alive = 1; //设置保活开启
    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &alive, sizeof(alive));
    int idle = 5; // 5秒钟无数据，触发保活机制，发送保活包
    setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
    int intv = 1; //如果没有收到回应，则5秒钟后重发保活包
    setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &intv, sizeof(intv));
    int cnt = 3; //连续3次没收到保活包，视为连接失效
    setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
    int opt = 1;
    int len = sizeof(opt);
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, len);
    int flag = 1;
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
}

int ClientSock::ConnectPollWait(int sockfd, int timeout)
{
    struct pollfd fd;
    memset(&fd, 0, sizeof(struct pollfd));

    fd.fd = fd;
    fd.events = POLLOUT | POLLIN;
    fd.revents = 0;
    errno = 0;

    for (;;) {
        switch (poll(&fd, 1, time_out)) {
            case -1:
                if (errno != EINTR) {
                    return -1;
                }
                continue;
            case 0:
                errno = ETIMEDOUT;
                return -1;
            default:
                if (fd.revents & POLLNVAL) {
                    return -1;
                }
                return 1;
        }
    }
    return 1;
}

int ClientSock::CreateConnect(const char *ip, int port, int timeout)
{
    if (timeout < 0) {
        m_err = "time oute value is illegal, must be >=0";
        return -1;
    }
    struct sockaddr_in addr;
    if (0 > (m_fd = socket(AF_INET, SOCK_STREAM, 0))) {
        m_err = strerror(errno);
        return -3;
    } else {
        memset(&addr, 0, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip);
        SetConnectOpt(m_fd);
        if (-1 == SetAsync(m_fd)) //设置非阻塞socket
        {
            m_err = strerror(errno);
            Close();
        } else {
            if (connect(m_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
                if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
                    m_err = strerror(errno);
                    Close();
                } else //处理阻塞，连接已经启动，但是尚未完成
                {
                    if (ConnectPollWait(m_fd, timeout) < 0) {
                        m_err = strerror(errno);
                        Close();
                    }
                }
            }
            if (-1 == SetAsync(m_fd, false)) // 设置为阻塞
            {
                m_err = strerror(errno);
                Close();
            }
        }
    }
    return m_fd;
}

int ClientSock::CreateConnect(const char *addr, int port, int timeout)
{
    if (timeout < 0) {
        m_err = "time oute value is illegal, must be >=0";
        return -1;
    }
    struct addrinfo hints, *result;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    char number[10];
    sprintf(number, "%d", port);
    int ret = getaddrinfo(addr, number, &hints, &result);
    if (0 != ret) {
        m_err = gai_strerror(ret);
    } else {
        m_fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
        if (-1 == m_fd) {
            m_err = strerror(errno);
        } else {
            int alive = 1; //设置保活开启
            setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE, &alive, sizeof(alive));
            int idle = 5; // 5秒钟无数据，触发保活机制，发送保活包
            setsockopt(m_fd, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
            int intv = 1; //如果没有收到回应，则5秒钟后重发保活包
            setsockopt(m_fd, SOL_TCP, TCP_KEEPINTVL, &intv, sizeof(intv));
            int cnt = 3; //连续3次没收到保活包，视为连接失效
            setsockopt(m_fd, SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
            int opt = 1;
            int len = sizeof(opt);
            setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, &opt, len);
            int flag = 1;
            ret = setsockopt(m_fd,          /* socket affected */
                             IPPROTO_TCP,   /* set option at TCP level */
                             TCP_NODELAY,   /* name of option */
                             (char *)&flag, /* the cast is historical cruft */
                             sizeof(int));  /* length of option value */
            ret = SetAsync(m_fd);           //设置非阻塞socket
            if (-1 == ret) {
                m_err = strerror(errno);
                Close();
            } else {
                if (-1 != connect(m_fd, result->ai_addr, result->ai_addrlen)) { //连接成功
                    ret = SetAsync(m_fd, false); //设置为阻塞
                    if (-1 == ret) {
                        m_err = strerror(errno);
                        Close();
                    }
                } else {
                    if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
                        m_err = strerror(errno);
                        Close();
                    } else //处理阻塞，连接已经启动，但是尚未完成
                    {
                        int n, error;
                        socklen_t len;
                        fd_set rset, wset;
                        struct timeval tval;
                        FD_ZERO(&rset);
                        FD_SET(m_fd, &rset);
                        wset = rset;
                        if (timeout == 0) {
                            n = select(m_fd + 1, &rset, &wset, NULL, NULL); //永不超时
                        } else {
                            tval.tv_sec = timeout / 1000;
                            tval.tv_usec = (timeout % 1000) * 1000;
                            n = select(m_fd + 1, &rset, &wset, NULL, &tval); //设置超时时间
                        }
                        if (0 == n) { //超时
                            Close();
                            errno = ETIMEDOUT;
                            m_err = strerror(errno);
                        }

                        if (FD_ISSET(m_fd, &rset) || FD_ISSET(m_fd, &wset)) {
                            len = sizeof(error);
                            if (getsockopt(m_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0 ||
                                error) { //处理套接字连接过程中错误
                                if (error) {
                                    errno = error;
                                }
                                m_err = strerror(errno);
                                Close();
                            } else {
                                if (-1 == SetAsync(m_fd, false)) // 设置为阻塞
                                {
                                    m_err = strerror(errno);
                                    Close();
                                } else {
                                    if (error) {
                                        errno = error;
                                        m_err = strerror(errno);
                                        Close();
                                    }
                                }
                            }
                        } else {
                            m_err = strerror(errno);
                            Close();
                        }
                    }
                }
            }
        }
    }
    freeaddrinfo(result);
    return m_fd;
}

int ClientSock::CloseConnect()
{
    return Close();
}
